#include "projectile.h"

#include "sector.h"
#include "level.h"
#include "line.h"

#include "bsp.h" // eTB_Polygon

#include "../../Epsilon/timer.h" // eTimer

std::vector <cli_parse::Projectile *> g_projectiles;

#include <set>
#include <algorithm>

// p = line's origin
// d = line's direction

int
RayIntersectsTriangle (e3dVector3& p,  e3dVector3& d, 
                       e3dVector3& v0, e3dVector3& v1, e3dVector3& v2) {


  e3dVector3 e1 (v1 - v0);
  e3dVector3 e2 (v2 - v0);

  // % = Cross Product
  e3dVector3 h ( d % e2);
  float      a (e1 * h );
  
  if (a > -0.00001f && a < 0.00001f)
    return (false);
  
  float f = 1.0f / a;

  e3dVector3 s (p - v0);

  float u = f * (s * h);
  
  if (u < 0.0 || u > 1.0)
    return (false);

  e3dVector3 q (s % e1);

  float v = f * (d * q);

  if (v < 0.0f || u + v > 1.0f)
    return (false);

  // at this stage we can compute t to find out where 
  // the intersection point is on the line
  float t = f * (e2 * q);
  if (t > 0.00001f) // ray intersection
    return (true);
  else // this means that there is a line intersection  
     // but not a ray intersection
     return (false);
}

// See: http://www.softsurfer.com/Archive/algorithm_0105/algorithm_0105.htm

int
PolyIntersection (eTB_Polygon* poly, e3dVector3& v1, e3dVector3& v2)
{
  e3dVector3 u,   v,  n;
  e3dVector3 dir, w0, w;
  float      r,   a,  b;

  u = poly->getVertex (1)->getPos () - poly->getVertex (2)->getPos ();
  v = poly->getVertex (2)->getPos () - poly->getVertex (0)->getPos ();

  n = u * v;

  if (n.IsZero ())
    return -1;

  dir = v2 - v1;
  w0  = v1 - poly->getVertex (0)->getPos ();

  a = -DotProduct (n, w0);
  b =  DotProduct (n, dir);

  const float EPSILON = 0.000001f;

  if (fabs (b) < EPSILON) {
    if (a == 0.0f)
      return 2;
    else
      return 0; 
  }

  r = a / b;

  if (r < 0.0f)
    return 0;

  e3dVector3 I;

  I = v1 + r * dir;

  float uu, uv, vv, wu, wv, D;

  uu = DotProduct (u, u);
  uv = DotProduct (u, v);
  vv = DotProduct (v, v);
   w = I - e3dVector3 (*poly->getVertex (0));
  wu = DotProduct (w, u);
  wv = DotProduct (w, v);
   D = uv * uv - uu * vv;

  float s, t;
  s = (uv * wv - vv * wu) / D;
  if (s < 0.0f || s > 1.0f)
    return 0;
  t = (uv * wu - uu * wv) / D;
  if (t < 0.0f || (s + t) > 1.0f)
    return 0;

  return 1;
}

int
OnRight (Underlight::Line* aLine, float Px, float Py)
{
  float x1, y1, x2, y2, temp;

  x1 = aLine->x1 ();
  y1 = aLine->y1 ();
  x2 = aLine->x2 ();
  y2 = aLine->y2 ();

  temp = ((x1 - x2) * (Py - y2)) - ((y1 - y2) * (Px - x2));

  if (temp < 0)
    return 1;	// not on right!

  return 0;
}


void
CalcCrossedLines ( Underlight::Sector*              from_sec,
                   e3dVector3&                      from_pos,
                   e3dVector3&                      to_pos,
                   std::set <Underlight::Sector *>& sectors,
                   std::set <Underlight::Line *>&   lines )
{
  /* Can't do anything without a valid starting sector. */
  if (from_sec == NULL)
    return;

  /* Don't check the same sector more than once. */
  if (sectors.find (from_sec) != sectors.end ())
    return;

  sectors.insert (from_sec);

  const float x = to_pos.x;
  const float y = to_pos.y;

  const float orig_x = from_pos.x;
  const float orig_y = from_pos.y;

  Underlight::Line* first_line = from_sec->firstline;
  Underlight::Line* line       = first_line;

  int cross_count = 0;

  std::set <Underlight::Line *> crossed_lines;

  do {
    if (OnRight (line, x, y) != OnRight (line, orig_x, orig_y)) {
      ++cross_count;
      crossed_lines.insert (line);
    } else {
      //eTB_Printf ("Not inside sector\n");
    }
  } while (line = line->nextline);

  std::set <Underlight::Line *>::iterator line_iter = crossed_lines.begin ();
  std::set <Underlight::Line* >::iterator last_line = crossed_lines.end   ();

  int i = 0;

  /* For Each Crossed Line, test all of the lines in its TWO sectors... */
  while (line_iter != last_line) {
    lines.insert ((*line_iter));

    /* One of these sectors (not sure which) will be the sector we are already
         testing. This is part of the reason for passing a list of already
           tested sectors each time CalcCrossedLines (...) is called. */
    CalcCrossedLines ((*line_iter)->sector,       from_pos, to_pos, sectors, lines);
    CalcCrossedLines ((*line_iter)->facingsector, from_pos, to_pos, sectors, lines);

    ++line_iter;
  }
}

void
cli_parse::Projectile::Update (void)
{
  static eTimer timer;
  timer.tick ();

  const float tick = (float)timer.milliseconds ();

  if (last_update == -1.0f) {
    last_update = (float)timer.milliseconds ();
    return;
  }

  const float elapsed = tick - last_update;

  Underlight::Sector* sector =
    Underlight::lvl->FindSector (origin.x, origin.y, NULL, true, true);

  std::set <Underlight::Sector *> sectors;
  std::set <Underlight::Line *>   lines;

  e3dVector3 new_pos (origin + ((velocity * speed) *(elapsed / 3.333f)));

  CalcCrossedLines (sector, origin, new_pos, sectors, lines);

//  std::map < Underlight::Line*, std::pair < eTB_Polygon* floor, eTB_Polygon* ceiling > > lines_to_polys;

  if (lines.size () > 0) {
    ////eTB_Printf ("Move crosses %d sectors (%d lines)\n", sectors.size (), lines.size ());

    std::set <Underlight::Line *>::iterator line       = lines.begin ();
    std::set <Underlight::Line *>::iterator last_line  = lines.end   ();

    while (line != last_line) {
      Underlight::Sector::Wall* fwall = (Underlight::Sector::Wall *)(*line)->fwall;
      Underlight::Sector::Wall* cwall = (Underlight::Sector::Wall *)(*line)->cwall;

      if (fwall != NULL) {
        eTB_Polygon* floor_poly = fwall->polygon;

        if (floor_poly != NULL) {
          e3dVector3 t0 (*floor_poly->getVertex (0));
          e3dVector3 t1 (*floor_poly->getVertex (1));
          e3dVector3 t2 (*floor_poly->getVertex (2));
          e3dVector3 t3 (*floor_poly->getVertex (3));

          if ( RayIntersectsTriangle (origin, velocity, t0, t1, t2) ||
               RayIntersectsTriangle (origin, velocity, t2, t3, t0)    ) {
            e3dVector3 tn ((t1 - t0) % (t2 - t0));
            tn.Normalize ();
            velocity = velocity - tn * (tn * velocity) * 2.0f;
            velocity.Normalize ();
            new_pos = origin;
//            velocity = e3dVector3 (0.0f, 0.0f, 0.0f);
//            eTB_Printf ("Crosses Floor Wall... (%d)\n", (*line)->num);
          }
        }
      }

      if (cwall != NULL) {
        eTB_Polygon* ceiling_poly = cwall->polygon;

        if (ceiling_poly != NULL) {
          e3dVector3 t0 (*ceiling_poly->getVertex (0));
          e3dVector3 t1 (*ceiling_poly->getVertex (1));
          e3dVector3 t2 (*ceiling_poly->getVertex (2));
          e3dVector3 t3 (*ceiling_poly->getVertex (3));

          if ( RayIntersectsTriangle (origin, velocity, t0, t1, t2) ||
               RayIntersectsTriangle (origin, velocity, t2, t3, t0)    ) {
            e3dVector3 tn ((t1 - t0) % (t2 - t0));
            tn.Normalize ();
            velocity = velocity - tn * (tn * velocity) * 2.0f;
            velocity.Normalize ();
            new_pos = origin;
//            velocity = e3dVector3 (0.0f, 0.0f, 0.0f);
//            eTB_Printf ("Crosses Ceiling Wall... (%d)\n", (*line)->num);
          }
        }
      }

      ++line;
    }
  }

  /* If the projectile hit something, elapsed time will be set to the
       time at which it would have hit the object... the next call to
         Update (...) will cause the remaining momentum to be carried
           out in the NEW direction. */
  //last_update += elapsed;
  last_update = tick;

  if (sector != NULL)
  if (! velocity.IsZero ()) {
    if (origin.z > sector->CeilHt (origin.x, origin.y)) {
      //eTB_Printf ("Hit the ceiling!\n");
      new_pos = origin;
      new_pos.z = sector->CeilHt (origin.x, origin.y);
      e3dVector3 tn (0.0f, 0.0f, -1.0f);
      velocity = velocity - tn * (tn * velocity) * 2.0f;
      velocity.Normalize ();
//      velocity = e3dVector3 (0.0f, 0.0f, 0.0f);
    }
    if (origin.z < sector->FloorHt (origin.x, origin.y)) {
      //eTB_Printf ("Hit the floor!\n");
      new_pos = origin;
      new_pos.z = sector->FloorHt (origin.x, origin.y);
      e3dVector3 tn (0.0f, 0.0f, 1.0f);
      velocity = velocity - tn * (tn * velocity) * 2.0f;
      velocity.Normalize ();
//      velocity = e3dVector3 (0.0f, 0.0f, 0.0f);
    }
  }

  if (sector == NULL) {
////    eTB_Printf ("Projectile Leaked - Purging!\n");
    std::vector <cli_parse::Projectile *>::iterator proj =
      std::find (g_projectiles.begin (), g_projectiles.end (), this);
    g_projectiles.erase (proj);
    delete this;
    return; /* Return immediately! - It is the only safe thing to do. */
  }

  origin = new_pos;
}



void
cli_parse::FireProjectile (e3dVector3 pos, e3dVector3 velocity)
{
  //eTB_Printf ("Firing projectile!\n");
  Projectile* fire = new Projectile (pos, velocity);
  g_projectiles.push_back (fire);
}

void
cli_parse::UpdateProjectiles (void)
{
  for (unsigned int i = 0; i < g_projectiles.size (); i++) {
    g_projectiles [i]->Update ();
  }
}

cli_parse::Projectile*
cli_parse::GetProjectile (int idx) {
  return g_projectiles [idx];
}

int
cli_parse::GetNumProjectiles (void)
{
  return g_projectiles.size ();
}


cli_parse::Projectile::Projectile (e3dVector3& pos, e3dVector3& vel)
{
  origin      = pos;
  velocity    = vel;
  velocity.x  = -vel.x;
  velocity.y  = -vel.y;
  velocity.z  = -vel.z;
  last_update = -1.0f;

  speed = fabs (velocity.Normal ());
  velocity.Normalize ();

  float red   = ((float)rand () / (float)RAND_MAX);
  float green = ((float)rand () / (float)RAND_MAX);
  float blue  = ((float)rand () / (float)RAND_MAX);

  color = e3dVector3 (red, green, blue);
  color.Normalize ();
}